package cn.edu.lcu.cs.javaprogramming.util;

import cn.edu.lcu.cs.javaprogramming.exception.DividedByZeroException;

import java.math.BigInteger;
import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;

/**
 * <p>数学工具类，演示：</p>
 * <ol>
 * <li>方法重载</li>
 * <li>方法签名</li>
 * <li>类型自动转换</li>
 * <li>可变数量的参数（参数个数在编码时未知）</li>
 * </ol>
 * 2022/3/1 23:52
 *
 * @Author ling
 */
public class Math {
    public static final double PI = 3.14159;

    /**
     * 工具类，所有成员都是静态的，可以通过类名直接访问，不需要实例化。 <br>
     * 如何防止客户程序错误调用构造方法从而创建类的实例呢？<br>
     * 构造方法私有，可以解决这个问题。
     */
    private Math() {
    }

    /**
     * 除法操作，实在没有啥实际意义，仅用于演示异常操作。
     *
     * @param dividend 被除数
     * @param divisor  除数
     * @return quotient商
     * @throws DividedByZeroException 可能抛出的异常
     */
    public static double divide(double dividend, double divisor) throws DividedByZeroException {
        if (divisor == 0) {
            throw new DividedByZeroException("除数不能为0！");
        }
        double quotient = dividend / divisor;
        return quotient;
    }

    /**
     * 返回两个整数的和
     *
     * @param x
     * @param y
     * @return 两个整型数值的和
     */
    public static int sum(int x, int y) {
        return x + y;
    }

    /**
     * 返回两个整数的和
     *
     * @param x
     * @param y
     * @return 两个长整型数值的和
     */
    public static long sum(long x, long y) {
        return x + y;
    }

    /**
     * 返回两个整数的和
     *
     * @param x 操作数1
     * @param y 操作数2
     * @return 两个短整型值的和
     */
    public static short sum(short x, short y) {
        // short + short，结果是int？Why？需要转型
        return (short) (x + y);
    }

    /**
     * <h4>可变数量的整数求和（至少2个操作数）</h4>
     *
     * @param x     操作数1，必需
     * @param y     操作数2，必需
     * @param other 剩余的操作数，0或多个，数量可变。
     * @return 若干操作数的和
     */
    public static int sum(int x, int y, int... other) {
        int result = x + y;

        // 参数other，带省略号，它的实质是一个数组
        // 必须作为参数列表的最后一项
        if (other != null && other.length > 0) {
            for (int operand : other) {
                result += operand;
            }
        }

        return result;
    }

    /**
     * 递归求阶乘。应该谨慎编码，让代码更安全。
     *
     * @param n
     * @return
     */
    public static long factorial(long n) {
        if (n > 1) {
            // 应该考虑结果溢出的情况。
            // 如果结果溢出了，应该发出一个警告，中断程序执行，而不是返回溢出后的错误结果。
            // 如果有溢出，正数会转换为负数。
            long result = n * factorial(n - 1);
            if (result > 0) {
                return result;
            } else {
                throw new RuntimeException("结果溢出：" + n + "! = " + result);
            }
        } else if (n == 1 || n == 0) {
            return 1;
        } else {
            // 如果输入非法，应该发出一个警告，中断程序执行，而不是返回-1。
            // 因为返回的结果-1，可能阴差阳错，被当作正确结果使用，造成无法想像的严重后果。
            //return -1;
            throw new IllegalArgumentException("参数非法：负数（" + n + "）不能求阶乘");
        }
    }

    /**
     * 用递归求阶乘，使用BigInteger保存结果。溢出的问题可以忽略不计。
     *
     * @param n
     * @return
     */
    public static BigInteger factorialSafe(long n) {
        if (n > 1) {
            // 应该考虑结果溢出的情况。???
            // BigInteger可以表示非常非常非常大的数，取决于电脑的内存？？
            BigInteger bigResult = factorialSafe(n - 1).multiply(BigInteger.valueOf(n));
            return bigResult;
        } else if (n == 1 || n == 0) {
            return BigInteger.ONE;
        } else {
            // 如果输入非法，应该发出一个警告，中断程序执行，而不是返回-1。
            // 若返回-1，可能阴差阳错，被当作正确结果使用，造成无法想像的严重后果。
            // ？？？
//            return -1;
            throw new IllegalArgumentException(n + " 的阶乘是无效的。");
        }
    }


    /**
     * 对双精度数做一元运算
     *
     * @param operand
     * @param operator
     * @return
     */
    public static double unaryOperation(double operand, UnaryOperator<Double> operator) {
        return operator.apply(operand);
    }

    /**
     * 对双精度数做二元运算
     *
     * @param operand1
     * @param operand2
     * @param operator
     * @return
     */
    public static double binaryOperation(double operand1, double operand2, BinaryOperator<Double> operator) {
        return operator.apply(operand1, operand2);
    }

}
